package districting;

import java.util.Comparator;
import java.util.HashSet;
import java.util.PriorityQueue;

public class HillClimbingMethod {

   private Nation nation;

   private DivisionMethod method;

   public HillClimbingMethod(Nation nation, DivisionMethod method) {
      this.nation = nation;
      this.method = method;
   }

   public void allocateDivisions() {
      for (State state : nation.getStates()) {
         allocateDivisions(state);
      }
   }

   public void allocateDivisions(State state) {
      PriorityQueue<Division> divisionsPriQ = new PriorityQueue<Division>(
            state.numDivisions, new DivisionVotingPopulationComparator());
      HashSet<Division> pausedDivisions = new HashSet<Division>();

      for (int i = 0; i < state.numDivisions; i++) {
         Division oldDivision = state.getDivisions(method).get(i);
         Division division = new Division(state.getName() + Integer.toString(i)
               + "_HILL", state.getId() * 100 + i, DivisionMethod.HILL_CLIMBING);
         for (PollingBooth pb : oldDivision.getBooths()) {
            division.addPB(pb);
         }
         divisionsPriQ.add(division);
         state.addDivision(division, DivisionMethod.HILL_CLIMBING);
      }

      boolean isAcceptable = state
            .withinPopulationDeviation(DivisionMethod.HILL_CLIMBING);

      while (!isAcceptable && !divisionsPriQ.isEmpty()) {
         Division division = divisionsPriQ.poll();
         Comparator<PollingBooth> comparator = new PollingBoothCompactnessComparatorB(
               division, method);
         PollingBooth bestPB = null;

         for (PollingBooth pb : state.getBooths()) {
            if (division.getBooths().contains(pb)) {
               continue;
            }

            boolean isSatisfactory = false;
            // Is the new polling booth contiguous
            if (division.getPolygon().intersects(pb.getPolygon())) {
               Division oldDivision = pb
                     .getDivision(DivisionMethod.HILL_CLIMBING);
               // Will changing this booth improve population equality
               if (division.getVotingPopulation() + pb.getTotalVotes() < oldDivision
                     .getVotingPopulation()) {
                  int numParts = oldDivision.getPolygon().getNumGeometries();
                  oldDivision.removePB(pb);
                  // Will changing this booth break up the other district
                  if (oldDivision.getPolygon().getNumGeometries() > numParts) {
                     isSatisfactory = false;
                  } else {
                     isSatisfactory = true;
                  }
                  oldDivision.addPB(pb);
               }
            }
            if (isSatisfactory) {
               if (bestPB == null) {
                  bestPB = pb;
               } else if (comparator.compare(pb, bestPB) == -1) {
                  bestPB = pb;
               }
            }
         }

         if (bestPB == null) {
            pausedDivisions.add(division);
         } else {
            Division oldDivision = bestPB
                  .getDivision(DivisionMethod.HILL_CLIMBING);
            System.out.println("HillReg.dE Switching PB: " + bestPB.getName()
                  + " from " + oldDivision.getName() + " to "
                  + division.getName() + " with "
                  + division.getVotingPopulation());
            oldDivision.removePB(bestPB);
            division.addPB(bestPB);
            divisionsPriQ.add(division);
            divisionsPriQ.addAll(pausedDivisions);
         }
         isAcceptable = state
               .withinPopulationDeviation(DivisionMethod.HILL_CLIMBING);
      }
   }
}
